package com.sidom.movieIndexer.service.google;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import static org.apache.commons.lang.Validate.notNull;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.google.gson.Gson;
import com.sidom.movieIndexer.domain.Movie;
import com.sidom.movieIndexer.domain.google.GoogleSearchResult;
import com.sidom.movieIndexer.domain.google.GsearchResult;
import static com.sidom.movieIndexer.domain.google.Match.NOT_FOUND;
import static com.sidom.movieIndexer.domain.google.Match.PERFECT_MATCH;
import static com.sidom.movieIndexer.domain.google.Match.MATCH;
import com.sidom.movieIndexer.domain.google.Response;

/**
 * {@inheritDoc}.
 * 
 * @author Siebe
 */
@Repository
public class GoogleSearchServiceImpl implements GoogleSearchService, InitializingBean{
	/**
	 * Logger for {@link GoogleSearchServiceImpl}.
	 */
	private Log log = LogFactory.getLog(GoogleSearchServiceImpl.class);
	
	/**
	 * googleSearchversion key value of the map of Rest paramters.
	 */
	private static final String GOOGLE_SEARCH_VERSION_KEY = "googleSearchversion";
	/**
	 * resultSize key value of the map of Rest paramters.
	 */
	private static final String RESULT_SIZE_KEY = "resultSize";
	/**
	 * queryString key value of the map of Rest paramters.
	 */
	private static final String QUERY_STRING_KEY = "queryString";
	
	/**
	 * The map of search parameters: googleSearchversion, resultSize, queryString.
	 */
	private Map<String, String> searchParameters;
	
	/**
	 * Search prefix to use in the search. Can be configured in movieIndexer.properties.
	 * Property injected by Spring.
	 */
	@Value("${searchPrefix}")
	private String searchPrefix;
	
	/**
	 * Google search version to use in the search. Can be configured in movieIndexer.properties.
	 * Property injected by Spring.
	 */
	@Value("${googleSearchversion}")
	private String googleSearchversion;
	
	/**
	 * The resultsize to use in the search. Can be configured in movieIndexer.properties.
	 * Property injected by Spring.
	 */
	@Value("${resultSize}")
	private String resultSize;
	
	/**
	 * Search prefix to use in the search. Can be configured in movieIndexer.properties.
	 * Property injected by Spring.
	 */
	@Value("${conditionsValidImdbUrl}")
	private String conditionsValidImdbUrl;
	
	/**
	 * The google search ajax api url to use. Can be configured in movieIndexer.properties.
	 * Property injected by Spring.
	 */
	@Value("${googleSearchUrl}")
	private String googleSearchUrl;
	
	/**
	 * RestTemplate from Spring used to make the rest call to google search ajax api.
	 * Property injected by Spring.
	 */
	@Autowired
	private RestTemplate restTemplate;
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Movie> addImdbLinks(final List<Movie> movies) {
		log.info("Add imdb links for movies: " + movies);
		notNull(movies, "Movies should not be null");
		for (Movie movie : movies) {
			Response response = performSearch(movie);
			setImdbUrl(response, movie);
		}
		log.trace("IMDB links added");
		return movies;
	}
	
	/**
	 * Execute the query to google search.
	 * @param movie The {@link Movie}.
	 * @return The Google {@link Response}.
	 */
	private Response performSearch(final Movie movie){
		notNull(movie.getMovieFile(), "The movie file should not be null");
		searchParameters.put(QUERY_STRING_KEY, searchPrefix + " " + movie.getMovieFile().getCleanedFileName());
		log.trace("Search parameters for: " + movie.getMovieFile().getCleanedFileName() + " are: " + searchParameters);

		String result = restTemplate.getForObject(googleSearchUrl + "?v={googleSearchversion}" +
																	"&rsz={resultSize}" +
																	"&q={queryString}",
												  String.class, 
												  searchParameters);
		log.trace("JSon result for: " + searchPrefix + " " + movie.getMovieFile().getCleanedFileName() + " is: " + result);

		return new Gson().fromJson(result, Response.class);
	}

	/**
	 * Set the imdb url of the {@link Movie} based on the Google {@link Response}.
	 * @param response The Google {@link Response}.
	 * @param movie The {@link Movie}.
	 */
	private void setImdbUrl(final Response response, final Movie movie) {
		GoogleSearchResult googleSearchResult = new GoogleSearchResult(response);
		movie.setGoogleSearchResult(googleSearchResult);
		boolean firstMatch = true;
		for(GsearchResult gsearchResult : response.getResponseData().getResults()){
			if(isImdbTitleUrl(gsearchResult.getUrl())){
				if (firstMatch) {
					googleSearchResult.setMatch(PERFECT_MATCH);
				}else{
					googleSearchResult.setMatch(MATCH);
				}
				googleSearchResult.setImdbUrl(gsearchResult.getUrl());
				return;
			}
			firstMatch = false;
		}
		googleSearchResult.setMatch(NOT_FOUND);
	}
	
	/**
	 * Check if the url is a title url of IMDB.
	 * Can be configured by setting the property {@link GoogleSearchService#conditionsValidImdbUrl}
	 * @param url The url
	 * @return True If the link contains all permissions
	 * 		   False if not
	 */
	private boolean isImdbTitleUrl(final String url) {
		Set<String> conditionsValidImdbUrl = StringUtils.commaDelimitedListToSet(this.conditionsValidImdbUrl);
		for (String condition : conditionsValidImdbUrl) {
			if(!url.contains(condition)){
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Set the search parameters.
	 * googleSearchversionKey: The version of google search ajax api.
	 * resultSizeKey: The size of the result set (see documentation movieIndexer.properties)
	 */
	private void createSearchParameters() {
		searchParameters = new HashMap<String, String>();
		searchParameters.put(GOOGLE_SEARCH_VERSION_KEY, googleSearchversion);
		searchParameters.put(RESULT_SIZE_KEY, resultSize);
	}
	
	/**
	 * Check the attributes for null after the properties have been set.
	 */
	@PostConstruct
	public void afterPropertiesSet() {
		notNull(searchPrefix);
		notNull(googleSearchversion);
		notNull(resultSize);
		notNull(conditionsValidImdbUrl);
		notNull(googleSearchUrl);
		createSearchParameters();
		log.debug("Prefix used: " + searchPrefix);
		log.debug("googleSearchversion used: " + googleSearchversion);
		log.debug("resultSize used: " + resultSize);
		log.debug("google search url used: " + googleSearchUrl);
	}
}
